package com.lidong.lowcode.database.utils;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.JarURLConnection;
import java.net.URL;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author LiDong
 * @version 1.0.0
 * @createTime 9/10/2022 11:32 AM
 */
@SuppressWarnings("all")
public final class ClassUtils {

    /**
     * 类名后缀
     */
    private static final String CLASS_SUFFIX = ".class";
    private static final String L_FILE = "file";
    private static final String L_JAR = "jar";
    private static final Pattern COMPILE_PATTERN_ONE = Pattern.compile(".", Pattern.LITERAL);
    private static final Pattern COMPILE_PATTERN_TWO = Pattern.compile("/", Pattern.LITERAL);
    private static final Pattern COMPILE_PATTERN_THREE = Pattern.compile("\\\\", Pattern.LITERAL);
    private static final Pattern COMPILE_PATTERN_FOUR = Pattern.compile("/", Pattern.LITERAL);
    private static final char POINT = '.';

    private ClassUtils() {
    }

    /**
     * 根据类接口查到所有的class
     *
     * @param clazz 接口文件
     * @return List
     */
    public static <T> List<Class<T>> getAllClassByInterface(Class<T> clazz) {
        List<Class<T>> list = new ArrayList<>();
        try {
            List<Class<T>> allClass = getAllClass(clazz.getPackage().getName());
            // 循环判断路径下的所有类是否实现了指定的接口 并且排除接口类自己
            for (Class<T> aClass : allClass) {
                if (clazz.isAssignableFrom(aClass)) {
                    if (!clazz.equals(aClass)) {
                        // 自身并不加进去
                        list.add(aClass);
                    }
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return list;
    }

    /**
     * 根据注解查到所有的 class
     *
     * @param clazz 接口文件
     * @return List
     */
    public static <T> List<Class<T>> getAllClassByAnnotation(Class<T> clazz) {
        try {
            List<Class<T>> allClass = getAllClass(clazz.getPackage().getName());
            return allClass.stream().filter((a) -> a.isAnnotationPresent((Class<? extends Annotation>) clazz)).collect(Collectors.toList());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 根据类接口查到所有的class(指定包名)
     *
     * @param clazz 接口文件
     * @return List
     */
    public static <T> List<Class<T>> getAllClassByAnnotation(Class<T> clazz, String packageName) {
        try {
            List<Class<T>> allClass = getAllClass(packageName);
            return allClass.stream().filter((a) -> a.isAnnotationPresent((Class<? extends Annotation>) clazz)).collect(Collectors.toList());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 从一个指定路径下查找所有的类
     *
     * @param packageName 包名
     * @return List
     */
    private static <T> List<Class<T>> getAllClass(String packageName) {
        List<String> classNameList = getClassPathsByPackage(packageName);
        List<? extends Class<?>> collect = classNameList.stream().map((name) -> {
            try {
                return Class.forName(name);
            } catch (Throwable e) {
                return null;
            }
        }).filter(Objects::nonNull).distinct().collect(Collectors.toList());
        return (List<Class<T>>) collect;
    }


    /**
     * 获取某包下所有类
     *
     * @param packageName 包名
     * @return List
     */
    public static List<String> getClassPathsByPackage(String packageName) {
        List<String> fileNames = null;
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        String packagePath = COMPILE_PATTERN_ONE.matcher(packageName).replaceAll(Matcher.quoteReplacement("/"));
        URL url = loader.getResource(packagePath);
        if (url != null) {
            String type = url.getProtocol();
            if (L_FILE.equals(type)) {
                String fileSearchPath = url.getPath();
                fileSearchPath = fileSearchPath.substring(0, fileSearchPath.indexOf("/classes"));
                fileNames = getClassPathsByFile(fileSearchPath);
            } else if (L_JAR.equals(type)) {
                try {
                    JarURLConnection jarUrlConnection = (JarURLConnection) url.openConnection();
                    JarFile jarFile = jarUrlConnection.getJarFile();
                    fileNames = getClassPathsByJar(jarFile);
                } catch (IOException e) {
                    throw new RuntimeException("Open package url failed：" + e.getMessage());
                }
            } else {
                throw new RuntimeException("File system not support for this type" + type);
            }
        }
        return fileNames;
    }

    /**
     * 从项目文件获取某包下所有类
     *
     * @param filePath 文件路径
     * @return List
     */
    private static List<String> getClassPathsByFile(String filePath) {
        List<String> classPaths = new ArrayList<>();
        try {
            Files.walkFileTree(Paths.get(new File(filePath).getAbsolutePath()), new PathSimpleFileVisitor(classPaths));
        } catch (Exception e) {
            throw new RuntimeException("walk files error!", e);
        }
        return classPaths;
    }

    /**
     * 从jar获取某包下所有类
     *
     * @param jarFile path
     * @return List
     */
    private static List<String> getClassPathsByJar(JarFile jarFile) {
        List<String> myClassName = new ArrayList<>();
        try {
            Enumeration<JarEntry> jarEntryEnumeration = jarFile.entries();
            while (jarEntryEnumeration.hasMoreElements()) {
                JarEntry jarEntry = jarEntryEnumeration.nextElement();
                String entryName = jarEntry.getName();
                if (entryName.endsWith(CLASS_SUFFIX)) {
                    entryName = COMPILE_PATTERN_THREE.matcher(COMPILE_PATTERN_TWO.matcher(entryName.replace(File.separator, ".")).replaceAll(Matcher.quoteReplacement("."))).replaceAll(Matcher.quoteReplacement(".")).substring(0, entryName.lastIndexOf('.'));
                    myClassName.add(entryName);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("Get classpath error occur:" + e.getMessage());
        }
        return myClassName;
    }

    private static final class PathSimpleFileVisitor extends SimpleFileVisitor<Path> {
        private final List<? super String> classPaths;

        PathSimpleFileVisitor(List<? super String> classPathList) {
            classPaths = classPathList;
        }

        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
            String filePath = file.toFile().getPath();
            if (filePath.endsWith(CLASS_SUFFIX)) {
                filePath = filePath.substring(filePath.indexOf(File.separator + "classes") + 9, filePath.lastIndexOf(POINT));
                filePath = COMPILE_PATTERN_FOUR.matcher(filePath.replace(File.separator, ".")).replaceAll(Matcher.quoteReplacement(".")).replace("\\", ".");
                classPaths.add(filePath);
            }
            return super.visitFile(file, attrs);
        }
    }
}
